<?php

namespace app\admin\library;

use app\admin\model\Admin;
use app\admin\model\AdminGroup;
use app\common\facade\Token;
use jueqi\Random;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Config;
use think\facade\Db;

/**
 * 管理员权限类
 */
class Auth extends \jueqi\Auth
{
    /**
     * @var Auth 对象实例
     */
    protected static $instance;

    /**
     * @var bool 是否登录
     */
    protected $loginEd = false;
    /**
     * @var string 错误消息
     */
    protected $error = '';
    /**
     * @var Admin Model实例
     */
    protected $model = null;
    /**
     * @var string 令牌
     */
    protected $token = '';
    /**
     * @var string 刷新令牌
     */
    protected $refreshToken = '';
    /**
     * @var int 令牌默认有效期
     */
    protected $keepTime = 86400;
    /**
     * @var string[] 允许输出的字段
     */
    protected $allowFields = ['id', 'username', 'nickname', 'avatar', 'last_login_time'];

    public function __construct(array $config = [])
    {
        parent::__construct($config);
    }

    /**
     * 初始化
     * @access public
     * @param array $options 参数
     * @return Auth
     */
    public static function instance(array $options = []): Auth
    {
        if (is_null(self::$instance)) {
            self::$instance = new static($options);
        }

        return self::$instance;
    }

    /**
     * 魔术方法-管理员信息字段
     * @param $name
     * @return null|string 字段信息
     */
    public function __get($name)
    {
        return $this->model ? $this->model->$name : null;
    }

    /**
     * 根据Token初始化管理员登录态
     * @param $token
     * @return bool
     */
    public function init($token): bool
    {
        try {
            if ($this->loginEd) {
                return true;
            }
            if ($this->error) {
                return false;
            }
            $tokenData = Token::get($token);
            if (!$tokenData) {
                return false;
            }
            $userId = intval($tokenData['user_id']);
            if ($userId > 0) {
                $this->model = (new Admin)->where('id', $userId)->find();
                if (!$this->model) {
                    $this->setError('Account not exist');
                    return false;
                }
                if ($this->model['status'] != '1') {
                    $this->setError('Account disabled');
                    return false;
                }
                $this->token = $token;
                // 登录成功
                $this->loginSuccessful();
                return true;
            } else {
                $this->setError('Token login failed');
                return false;
            }
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            return false;
        }
    }

    /**
     * 管理员登录成功
     * @return bool
     */
    public function loginSuccessful(): bool
    {
        if (!$this->model) {
            return false;
        }
        Db::startTrans();
        $this->model->login_failure   = 0; // 登录失败次数
        $this->model->last_login_time = time(); // 登录时间
        $this->model->last_login_ip   = getClientIp(); // 登录IP
        $this->model->save();
        $this->loginEd = true;

        if (empty($this->token)) {
            $this->token = Random::uuid();
            Token::set($this->token, 'admin', $this->model->id, $this->keepTime);
        }
        Db::commit();
        return true;
    }

    /**
     * 管理员登录
     * @param string $username
     * @param string $password
     * @param bool $keep_time
     * @return bool
     */
    public function login(string $username, string $password, bool $keep_time = false): bool
    {
        try {
            // 获取token刷新时间
            $refreshTokenTime = Config::get("backstage.refresh_token_time");
            $this->model      = (new Admin)->where('username', $username)->find();
            if (!$this->model) {
                $this->setError('Username is incorrect');
                return false;
            }
            if ($this->model['status'] != '1') {
                $this->setError('Account disabled');
                return false;
            }
            // 管理员登录失败次数
            $adminLoginRetry = Config::get('backstage.admin_login_retry');
            // 管理员登录失败间隔天数
            $adminLoginIntervalDays = Config::get('backstage.admin_login_interval_days');
            // 登录时间
            $lastLoginTime = $this->model->last_login_time ?? 0;
            if (is_numeric($lastLoginTime) === false) {
                $lastLoginTime = strtotime($lastLoginTime);
            }
            // 登录间隔时间
            $intervalTime = time() - $lastLoginTime;
            if ($adminLoginRetry && $this->model->login_failure >= $adminLoginRetry && $intervalTime < ($adminLoginIntervalDays * 86400)) {
                $this->setError('Please try again after ' . $adminLoginIntervalDays . ' day');
                return false;
            }
            if ($this->model->password != encrypt_password($password, $this->model->salt)) {
                // 登录失败
                $this->loginFailed();
                $this->setError('Password is incorrect');
                return false;
            }

            if ($keep_time) {
                // 刷新token
                $this->setRefreshToken($refreshTokenTime);
            }
            // 登录成功
            $this->loginSuccessful();
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            $this->setError('Login exception!');
            return false;
        }
        return true;
    }

    /**
     * 管理员登录失败
     * @return bool
     */
    public function loginFailed(): bool
    {
        if (!$this->model) {
            return false;
        }
        // 开启事务
        Db::startTrans();
        $this->model->login_failure++;
        $this->model->last_login_time = time();
        $this->model->last_login_ip   = getClientIp();
        $this->model->save();
        $this->token   = '';
        $this->model   = null;
        $this->loginEd = false;
        // 提交事务
        Db::commit();
        return true;
    }

    /**
     * 退出登录
     * @return bool
     */
    public function logout(): bool
    {
        if (!$this->loginEd) {
            $this->setError('You are not logged in');
            return false;
        }
        $this->loginEd = false;
        // 删除token
        Token::delete($this->token);
        $this->token = '';
        return true;
    }

    /**
     * 是否登录
     * @return bool
     */
    public function isLogin(): bool
    {
        return $this->loginEd;
    }

    /**
     * 获取管理员模型
     * @return Admin
     */
    public function getAdmin(): Admin
    {
        return $this->model;
    }

    /**
     * 获取管理员信息 - 只输出允许输出的字段
     * @return array
     */
    public function getInfo(): array
    {
        if (!$this->model) {
            return [];
        }
        $info                 = $this->model->toArray();
        $info                 = array_intersect_key($info, array_flip($this->getAllowFields()));
        $info['token']        = $this->getToken();
        $info['refreshToken'] = $this->getRefreshToken();
        return $info;
    }

    /**
     * 获取允许输出字段
     * @return string[]
     */
    public function getAllowFields(): array
    {
        return $this->allowFields;
    }

    /**
     * 设置允许输出字段
     * @param $fields
     */
    public function setAllowFields($fields)
    {
        $this->allowFields = $fields;
    }

    /**
     * 获取管理员Token
     * @return string
     */
    public function getToken(): string
    {
        return $this->token;
    }

    /**
     * 获取管理员刷新Token
     * @return string
     */
    public function getRefreshToken(): string
    {
        return $this->refreshToken;
    }

    /**
     * 设置刷新Token
     * @param int $keepTime
     */
    public function setRefreshToken(int $keepTime = 0)
    {
        $this->refreshToken = Random::uuid();
        Token::set($this->refreshToken, 'admin-refresh', $this->model->id, $keepTime);
    }

    /**
     * 设置Token有效期
     * @param int $keep_time
     */
    public function setKeepTime(int $keep_time = 0)
    {
        $this->keepTime = $keep_time;
    }

    /**
     * 检查是否有某权限
     * @param string $name
     * @param int $admin_id
     * @param string $relation
     * @param string $mode
     * @return bool
     */
    public function check(string $name, int $admin_id = 0, string $relation = 'or', string $mode = 'url'): bool
    {
        return parent::check($name, $admin_id ?: $this->id, $relation, $mode);
    }

    /**
     * 获取用户所有分组和对应权限规则
     * @param int $admin_id
     * @return array
     */
    public function getGroups(int $admin_id = 0): array
    {
        return parent::getGroups($admin_id ?: $this->id);
    }

    /**
     * 获取用户所有分组
     * @param int $admin_id
     * @return array
     */
    public function getGroupsAccess(int $admin_id = 0): array
    {
        return parent::getGroupsAccess($admin_id ?: $this->id);
    }

    /**
     * 获得权限规则列表
     * @param int $admin_id
     * @return array
     */
    public function getRuleList(int $admin_id = 0): array
    {
        return parent::getRuleList($admin_id ?: $this->id);
    }

    /**
     * 获取菜单规则列表
     * @access public
     * @param int $admin_id 用户ID
     * @return array
     */
    public function getMenus(int $admin_id = 0): array
    {
        return parent::getMenus($admin_id ?: $this->id);
    }

    public function isSuperAdmin(): bool
    {
        return in_array('*', $this->getRuleIds());
    }

    /**
     * 获取权限规则ids
     * @param int $admin_id
     * @return array
     */
    public function getRuleIds(int $admin_id = 0): array
    {
        return parent::getRuleIds($admin_id ?: $this->id);
    }

    /**
     * 获取管理员所在分组的所有子级分组
     * @return array
     */
    public function getAdminChildGroups(): array
    {
        try {
            $groupIds = Db::name('admin_group_access')
                ->where('admin_id', $this->id)
                ->select();
            $children = [];
            foreach ($groupIds as $group) {
                $this->getGroupChildGroups($group['group_id'], $children);
            }
            return array_unique($children);
        } catch (DataNotFoundException|DbException|ModelNotFoundException $e) {
            return [];
        }
    }

    /**
     * 获取某个管理员分组下面的所有子数组
     * @param $groupId
     * @param $children
     * @return void
     */
    public function getGroupChildGroups($groupId, &$children)
    {
        try {
            $childrenTemp = (new AdminGroup)->where('pid', $groupId)->where('status', '1')->select();
            foreach ($childrenTemp as $item) {
                $children[] = $item['id'];
                $this->getGroupChildGroups($item['id'], $children);
            }
        } catch (DataNotFoundException|DbException|ModelNotFoundException $e) {

        }
    }

    /**
     * 获取分组内的管理员
     * @param array $groups
     * @return array 管理员数组
     */
    public function getGroupAdmins(array $groups): array
    {
        return Db::name('admin_group_access')
            ->where('group_id', 'in', $groups)
            ->column('admin_id');
    }

    /**
     * 获取拥有"所有权限"的分组
     * @param string $dataLimit 数据权限
     * @return array 分组数组
     */
    public function getAllAuthGroups(string $dataLimit): array
    {
        // 当前管理员拥有的权限
        try {
            $rules         = $this->getRuleIds();
            $allAuthGroups = [];
            $groups        = (new AdminGroup)->where('status', '1')->select();
            foreach ($groups as $group) {
                if ($group['rules'] == '*') {
                    continue;
                }
                $groupRules = explode(',', $group['rules']);

                // 及时break, array_diff 等没有 in_array 快
                $all = true;
                foreach ($groupRules as $groupRule) {
                    if (!in_array($groupRule, $rules)) {
                        $all = false;
                        break;
                    }
                }
                if ($all) {
                    if ($dataLimit == 'allAuth' || ($dataLimit == 'allAuthAndOthers' && array_diff($rules, $groupRules))) {
                        $allAuthGroups[] = $group['id'];
                    }
                }
            }
            return $allAuthGroups;
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            return [];
        }
    }

    /**
     * 获取错误消息
     * @return float|int|string
     */
    public function getError()
    {
        return $this->error ? __($this->error) : '';
    }

    /**
     * 设置错误消息
     * @param $error
     * @return $this
     */
    public function setError($error): Auth
    {
        $this->error = $error;
        return $this;
    }
}
